package com.gmcloud.common.log.interceptor;

import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.TableNameParser;
import com.gmcloud.common.core.utils.SpringContextHolder;
import com.gmcloud.common.log.DataLogConfiguration;
import com.gmcloud.common.core.annotation.DataUpdateLog;
import com.gmcloud.common.log.event.SysDataLogEvent;
import com.gmcloud.common.log.util.DataCompareResult;
import com.gmcloud.common.log.util.DataLogUtils;
import com.gmcloud.common.log.util.SysLogUtils;
import com.gmcloud.common.utils.JsonUtil;
import com.gmcloud.upms.api.system.entity.SysDataLog;
import org.apache.commons.collections4.MapUtils;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.Configuration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;

import java.time.LocalDateTime;
import java.util.*;

@Intercepts({@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})})
public class DataLogInterceptor implements Interceptor {

    private static final Logger logger = LoggerFactory.getLogger("SYS_DATA_LOG");
    private static final String DATA_LOG_TABLE_NAME = "sys_data_log";

    private static final String LOG_TABLE_NAME = "sys_log";

    private final DataLogConfiguration dataLogConfiguration;

    public DataLogInterceptor(DataLogConfiguration dataLogConfiguration) {
        this.dataLogConfiguration = dataLogConfiguration;
    }

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        try {
            // 未启用数据日志则跳过
            if (!dataLogConfiguration.isEnabled()) {
                return invocation.proceed();
            }

            MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
            BoundSql boundSql = mappedStatement.getBoundSql(invocation.getArgs()[1]);
            Configuration configuration = mappedStatement.getConfiguration();
            String preparedSql = boundSql.getSql();

            // 如果是新增操作且未启用新增日志，则跳过
            if (SqlCommandType.INSERT.equals(mappedStatement.getSqlCommandType()) && !dataLogConfiguration.isIncludeInsert()) {
                return invocation.proceed();
            }
            // 忽略数据日志表操作
            if (preparedSql.contains(DATA_LOG_TABLE_NAME) || preparedSql.contains(LOG_TABLE_NAME)) {
                return invocation.proceed();
            }
            // SQL语句
            String sql = DataLogUtils.getSql(configuration, boundSql);
            // 表名
            Collection<String> tables = new TableNameParser(sql).tables();
            if (CollectionUtils.isEmpty(tables)) {// 如果找不到表则跳过
                return invocation.proceed();
            }
            String tableName = CollectionUtils.isNotEmpty(tables) ? tables.iterator().next() : "";// 多个表取第一个表名
            // 实体
            TableInfo tableInfo = TableInfoHelper.getTableInfos().stream().filter(t -> t.getTableName().equals(tableName)).findFirst().orElse(null);// 取第一个实体
            if (tableInfo == null) {// 如果找不到实体类则跳过
                return invocation.proceed();
            }
            Class<?> entityType = tableInfo.getEntityType();
            if (entityType == null) {// 如果找不到实体类则跳过
                return invocation.proceed();
            }
            if (!entityType.isAnnotationPresent(DataUpdateLog.class)) {// 如果实体没有数据日志注解则跳过
                return invocation.proceed();
            }

            // 保存日志(只处理使用Mybatis更新函数的数据)
            Object et = invocation.getArgs()[1];
            if (et instanceof Map map) {
                String key = "et";
                String listKey = "collection";
                if (map.containsKey(key)) {
                    this.saveLog(mappedStatement.getSqlCommandType(), sql, tableInfo, map.get(key));
                } else if (map.containsKey(listKey) && map.get(listKey) instanceof Collection list) {
                    for (Object o : list) {
                        this.saveLog(mappedStatement.getSqlCommandType(), sql, tableInfo, o);
                    }
                }
            } else {
                this.saveLog(mappedStatement.getSqlCommandType(), sql, tableInfo, et);
            }


        } catch (Exception e) {
            logger.warn("数据日志保存失败", e);
        }

        return invocation.proceed();

    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
        // TODO document why this method is empty
    }


    /**
     * 保存日志
     */
    private void saveLog(SqlCommandType sqlCommandType, String sql, TableInfo tableInfo, Object entity) throws Exception {
        // 日志内容
        SysDataLog dataLog = new SysDataLog();
        Map<String,Object> user = getUser();
        if (MapUtils.isNotEmpty(user)){
            dataLog.setOperatorId((Long) user.get("id"));
            dataLog.setCreateBy((String) user.get("username"));
        }
        // 操作类型
        dataLog.setType(sqlCommandType.name());

        dataLog.setCreateTime(LocalDateTime.now());
        // 类名和方法名
        String[] classAndMethod = DataLogUtils.getClassAndMethod();
        dataLog.setClassName(classAndMethod[0]);
        dataLog.setMethodName(classAndMethod[1]);
        // SQL语句
        dataLog.setStatement(sql);
        // 表名
        dataLog.setTableName(tableInfo.getTableName());
        // 实体名
        dataLog.setEntityName(tableInfo.getEntityType().getSimpleName());
        // 比较修改前后数据
        DataCompareResult dataCompareResult = DataLogUtils.getDataCompare(sqlCommandType, sql, tableInfo, entity);
        // 主键
        dataLog.setTableId(Optional.ofNullable(dataCompareResult.getId()).map(String::valueOf).orElse(""));
        // 数据变动
        dataLog.setDataChange(dataCompareResult.getDataChange().size() > 0 ? JsonUtil.objToString(dataCompareResult.getDataChange()) : "");

        logger.info("sql update: {}", dataLog);
        // 保存日志
        SpringContextHolder.publishEvent(new SysDataLogEvent(dataLog));

    }

    /**
     * 获取用户名称
     *
     * @return username
     */
    private static Map<String,Object> getUser() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if (authentication == null) {
            return Collections.emptyMap();
        }
        return BeanUtil.beanToMap(authentication.getPrincipal());
    }

}
